/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/** \file
 * \ingroup RNA
 */

#include <stdlib.h>

#include "DNA_anim_types.h"
#include "DNA_action_types.h"
#include "DNA_scene_types.h"

#include "BLI_utildefines.h"

#include "MEM_guardedalloc.h"

#include "RNA_access.h"
#include "RNA_define.h"
#include "RNA_enum_types.h"

#include "rna_internal.h"

#include "WM_api.h"
#include "WM_types.h"

#ifdef RNA_RUNTIME

#  include <stdio.h>
#  include <math.h>

/* needed for some of the validation stuff... */
#  include "BKE_animsys.h"
#  include "BKE_fcurve.h"
#  include "BKE_nla.h"

#  include "DNA_object_types.h"

#  include "ED_anim_api.h"

#  include "DEG_depsgraph_build.h"
#  include "DEG_depsgraph.h"

/* temp constant defined for these funcs only... */
#  define NLASTRIP_MIN_LEN_THRESH 0.1f

static void rna_NlaStrip_name_set(PointerRNA *ptr, const char *value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  /* copy the name first */
  BLI_strncpy_utf8(data->name, value, sizeof(data->name));

  /* validate if there's enough info to do so */
  if (ptr->owner_id) {
    AnimData *adt = BKE_animdata_from_id(ptr->owner_id);
    BKE_nlastrip_validate_name(adt, data);
  }
}

static char *rna_NlaStrip_path(PointerRNA *ptr)
{
  NlaStrip *strip = (NlaStrip *)ptr->data;
  AnimData *adt = BKE_animdata_from_id(ptr->owner_id);

  /* if we're attached to AnimData, try to resolve path back to AnimData */
  if (adt) {
    NlaTrack *nlt;
    NlaStrip *nls;

    for (nlt = adt->nla_tracks.first; nlt; nlt = nlt->next) {
      for (nls = nlt->strips.first; nls; nls = nls->next) {
        if (nls == strip) {
          /* XXX but if we animate like this, the control will never work... */
          char name_esc_nlt[sizeof(nlt->name) * 2];
          char name_esc_strip[sizeof(strip->name) * 2];

          BLI_strescape(name_esc_nlt, nlt->name, sizeof(name_esc_nlt));
          BLI_strescape(name_esc_strip, strip->name, sizeof(name_esc_strip));
          return BLI_sprintfN(
              "animation_data.nla_tracks[\"%s\"].strips[\"%s\"]", name_esc_nlt, name_esc_strip);
        }
      }
    }
  }

  /* no path */
  return BLI_strdup("");
}

static void rna_NlaStrip_update(Main *bmain, Scene *UNUSED(scene), PointerRNA *ptr)
{
  ID *id = ptr->owner_id;

  ANIM_id_update(bmain, id);
}

static void rna_NlaStrip_dependency_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
  DEG_relations_tag_update(bmain);

  rna_NlaStrip_update(bmain, scene, ptr);
}

static void rna_NlaStrip_transform_update(Main *bmain, Scene *scene, PointerRNA *ptr)
{
  NlaStrip *strip = (NlaStrip *)ptr->data;

  BKE_nlameta_flush_transforms(strip);

  /* set the flag */
  if ((strip->flag & NLASTRIP_FLAG_AUTO_BLENDS) && ptr->owner_id) {
    /* validate state to ensure that auto-blend gets applied immediately */
    IdAdtTemplate *iat = (IdAdtTemplate *)ptr->owner_id;

    if (iat->adt) {
      BKE_nla_validate_state(iat->adt);
    }
  }

  rna_NlaStrip_update(bmain, scene, ptr);
}

static void rna_NlaStrip_start_frame_set(PointerRNA *ptr, float value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  /* Clamp value to lie within valid limits:
   * - Cannot start past the end of the strip + some flexibility threshold.
   * - Cannot start before the previous strip (if present) ends.
   *   -> But if it was a transition,
   *   we could go up to the start of the strip + some flexibility threshold.
   *   as long as we re-adjust the transition afterwards.
   * - Minimum frame is -MAXFRAME so that we don't get clipping on frame 0.
   */
  if (data->prev) {
    if (data->prev->type == NLASTRIP_TYPE_TRANSITION) {
      CLAMP(
          value, data->prev->start + NLASTRIP_MIN_LEN_THRESH, data->end - NLASTRIP_MIN_LEN_THRESH);

      /* re-adjust the transition to stick to the endpoints of the action-clips */
      data->prev->end = value;
    }
    else {
      CLAMP(value, data->prev->end, data->end - NLASTRIP_MIN_LEN_THRESH);
    }
  }
  else {
    CLAMP(value, MINAFRAME, data->end);
  }
  data->start = value;
}

static void rna_NlaStrip_end_frame_set(PointerRNA *ptr, float value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  /* clamp value to lie within valid limits
   * - must not have zero or negative length strip, so cannot start before the first frame
   *   + some minimum-strip-length threshold
   * - cannot end later than the start of the next strip (if present)
   *   -> but if it was a transition,
   *   we could go up to the start of the end - some flexibility threshold
   *   as long as we re-adjust the transition afterwards
   */
  if (data->next) {
    if (data->next->type == NLASTRIP_TYPE_TRANSITION) {
      CLAMP(
          value, data->start + NLASTRIP_MIN_LEN_THRESH, data->next->end - NLASTRIP_MIN_LEN_THRESH);

      /* readjust the transition to stick to the endpoints of the action-clips */
      data->next->start = value;
    }
    else {
      CLAMP(value, data->start + NLASTRIP_MIN_LEN_THRESH, data->next->start);
    }
  }
  else {
    CLAMP(value, data->start + NLASTRIP_MIN_LEN_THRESH, MAXFRAME);
  }
  data->end = value;

  /* calculate the lengths the strip and its action (if applicable) */
  if (data->type == NLASTRIP_TYPE_CLIP) {
    float len, actlen;

    len = data->end - data->start;
    actlen = data->actend - data->actstart;
    if (IS_EQF(actlen, 0.0f)) {
      actlen = 1.0f;
    }

    /* now, adjust the 'scale' setting to reflect this (so that this change can be valid) */
    data->scale = len / ((actlen)*data->repeat);
  }
}

static void rna_NlaStrip_scale_set(PointerRNA *ptr, float value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  /* set scale value */
  /* NOTE: these need to be synced with the values in the
   * property definition in rna_def_nlastrip() */
  CLAMP(value, 0.0001f, 1000.0f);
  data->scale = value;

  /* adjust the strip extents in response to this */
  BKE_nlastrip_recalculate_bounds(data);
}

static void rna_NlaStrip_repeat_set(PointerRNA *ptr, float value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  /* set repeat value */
  /* NOTE: these need to be synced with the values in the
   * property definition in rna_def_nlastrip() */
  CLAMP(value, 0.01f, 1000.0f);
  data->repeat = value;

  /* adjust the strip extents in response to this */
  BKE_nlastrip_recalculate_bounds(data);
}

static void rna_NlaStrip_blend_in_set(PointerRNA *ptr, float value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;
  float len;

  /* blend-in is limited to the length of the strip, and also cannot overlap with blendout */
  len = (data->end - data->start) - data->blendout;
  CLAMP(value, 0, len);

  data->blendin = value;
}

static void rna_NlaStrip_blend_out_set(PointerRNA *ptr, float value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;
  float len;

  /* blend-out is limited to the length of the strip */
  len = (data->end - data->start);
  CLAMP(value, 0, len);

  /* it also cannot overlap with blendin */
  if ((len - value) < data->blendin) {
    value = len - data->blendin;
  }

  data->blendout = value;
}

static void rna_NlaStrip_use_auto_blend_set(PointerRNA *ptr, bool value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  if (value) {
    /* set the flag */
    data->flag |= NLASTRIP_FLAG_AUTO_BLENDS;

    /* validate state to ensure that auto-blend gets applied immediately */
    if (ptr->owner_id) {
      IdAdtTemplate *iat = (IdAdtTemplate *)ptr->owner_id;

      if (iat->adt) {
        BKE_nla_validate_state(iat->adt);
      }
    }
  }
  else {
    /* clear the flag */
    data->flag &= ~NLASTRIP_FLAG_AUTO_BLENDS;

    /* clear the values too, so that it's clear that there has been an effect */
    /* TODO: it's somewhat debatable whether it's better to leave these in instead... */
    data->blendin = 0.0f;
    data->blendout = 0.0f;
  }
}

static int rna_NlaStrip_action_editable(PointerRNA *ptr, const char **UNUSED(r_info))
{
  NlaStrip *strip = (NlaStrip *)ptr->data;

  /* strip actions shouldn't be editable if NLA tweakmode is on */
  if (ptr->owner_id) {
    AnimData *adt = BKE_animdata_from_id(ptr->owner_id);

    if (adt) {
      /* active action is only editable when it is not a tweaking strip */
      if ((adt->flag & ADT_NLA_EDIT_ON) || (adt->actstrip) || (adt->tmpact)) {
        return 0;
      }
    }
  }

  /* check for clues that strip probably shouldn't be used... */
  if (strip->flag & NLASTRIP_FLAG_TWEAKUSER) {
    return 0;
  }

  /* should be ok, though we may still miss some cases */
  return PROP_EDITABLE;
}

static void rna_NlaStrip_action_start_frame_set(PointerRNA *ptr, float value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  /* prevent start frame from occurring after end of action */
  CLAMP(value, MINAFRAME, data->actend);
  data->actstart = value;

  /* adjust the strip extents in response to this */
  /* TODO: should the strip be moved backwards instead as a special case? */
  BKE_nlastrip_recalculate_bounds(data);
}

static void rna_NlaStrip_action_end_frame_set(PointerRNA *ptr, float value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  /* prevent end frame from starting before start of action */
  CLAMP(value, data->actstart, MAXFRAME);
  data->actend = value;

  /* adjust the strip extents in response to this */
  BKE_nlastrip_recalculate_bounds(data);
}

static void rna_NlaStrip_animated_influence_set(PointerRNA *ptr, bool value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  if (value) {
    /* set the flag, then make sure a curve for this exists */
    data->flag |= NLASTRIP_FLAG_USR_INFLUENCE;
    BKE_nlastrip_validate_fcurves(data);
  }
  else {
    data->flag &= ~NLASTRIP_FLAG_USR_INFLUENCE;
  }
}

static void rna_NlaStrip_animated_time_set(PointerRNA *ptr, bool value)
{
  NlaStrip *data = (NlaStrip *)ptr->data;

  if (value) {
    /* set the flag, then make sure a curve for this exists */
    data->flag |= NLASTRIP_FLAG_USR_TIME;
    BKE_nlastrip_validate_fcurves(data);
  }
  else {
    data->flag &= ~NLASTRIP_FLAG_USR_TIME;
  }
}

static FCurve *rna_NlaStrip_fcurve_find(NlaStrip *strip,
                                        ReportList *reports,
                                        const char *data_path,
                                        int index)
{
  if (data_path[0] == '\0') {
    BKE_report(reports, RPT_ERROR, "F-Curve data path empty, invalid argument");
    return NULL;
  }

  /* Returns NULL if not found. */
  return list_find_fcurve(&strip->fcurves, data_path, index);
}

static NlaStrip *rna_NlaStrip_new(ID *id,
                                  NlaTrack *track,
                                  Main *bmain,
                                  bContext *C,
                                  ReportList *reports,
                                  const char *UNUSED(name),
                                  int start,
                                  bAction *action)
{
  NlaStrip *strip = BKE_nlastrip_new(action);

  if (strip == NULL) {
    BKE_report(reports, RPT_ERROR, "Unable to create new strip");
    return NULL;
  }

  strip->end += (start - strip->start);
  strip->start = start;

  if (BKE_nlastrips_add_strip(&track->strips, strip) == 0) {
    BKE_report(
        reports,
        RPT_ERROR,
        "Unable to add strip (the track does not have any space to accommodate this new strip)");
    BKE_nlastrip_free(NULL, strip, true);
    return NULL;
  }

  /* create dummy AnimData block so that BKE_nlastrip_validate_name()
   * can be used to ensure a valid name, as we don't have one here...
   * - only the nla_tracks list is needed there, which we aim to reverse engineer here...
   */
  {
    AnimData adt = {NULL};
    NlaTrack *nlt, *nlt_p;

    /* 'first' NLA track is found by going back up chain of given
     * track's parents until we fall off. */
    nlt_p = track;
    nlt = track;
    while ((nlt = nlt->prev) != NULL) {
      nlt_p = nlt;
    }
    adt.nla_tracks.first = nlt_p;

    /* do the same thing to find the last track */
    nlt_p = track;
    nlt = track;
    while ((nlt = nlt->next) != NULL) {
      nlt_p = nlt;
    }
    adt.nla_tracks.last = nlt_p;

    /* now we can just auto-name as usual */
    BKE_nlastrip_validate_name(&adt, strip);
  }

  WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_ADDED, NULL);

  DEG_relations_tag_update(bmain);
  DEG_id_tag_update_ex(bmain, id, ID_RECALC_ANIMATION | ID_RECALC_COPY_ON_WRITE);

  return strip;
}

static void rna_NlaStrip_remove(
    ID *id, NlaTrack *track, Main *bmain, bContext *C, ReportList *reports, PointerRNA *strip_ptr)
{
  NlaStrip *strip = strip_ptr->data;
  if (BLI_findindex(&track->strips, strip) == -1) {
    BKE_reportf(
        reports, RPT_ERROR, "NLA strip '%s' not found in track '%s'", strip->name, track->name);
    return;
  }

  BKE_nlastrip_free(&track->strips, strip, true);
  RNA_POINTER_INVALIDATE(strip_ptr);

  WM_event_add_notifier(C, NC_ANIMATION | ND_NLA | NA_REMOVED, NULL);

  DEG_relations_tag_update(bmain);
  DEG_id_tag_update_ex(bmain, id, ID_RECALC_ANIMATION | ID_RECALC_COPY_ON_WRITE);
}

/* Set the 'solo' setting for the given NLA-track, making sure that it is the only one
 * that has this status in its AnimData block.
 */
static void rna_NlaTrack_solo_set(PointerRNA *ptr, bool value)
{
  NlaTrack *data = (NlaTrack *)ptr->data;
  AnimData *adt = BKE_animdata_from_id(ptr->owner_id);
  NlaTrack *nt;

  if (data == NULL) {
    return;
  }

  /* firstly, make sure 'solo' flag for all tracks is disabled */
  for (nt = data; nt; nt = nt->next) {
    nt->flag &= ~NLATRACK_SOLO;
  }
  for (nt = data; nt; nt = nt->prev) {
    nt->flag &= ~NLATRACK_SOLO;
  }

  /* now, enable 'solo' for the given track if appropriate */
  if (value) {
    /* set solo status */
    data->flag |= NLATRACK_SOLO;

    /* set solo-status on AnimData */
    adt->flag |= ADT_NLA_SOLO_TRACK;
  }
  else {
    /* solo status was already cleared on track */

    /* clear solo-status on AnimData */
    adt->flag &= ~ADT_NLA_SOLO_TRACK;
  }
}

#else

/* enum defines exported for rna_animation.c */
const EnumPropertyItem rna_enum_nla_mode_blend_items[] = {
    {NLASTRIP_MODE_REPLACE,
     "REPLACE",
     0,
     "Replace",
     "The strip values replace the accumulated results by amount specified by influence"},
    {NLASTRIP_MODE_COMBINE,
     "COMBINE",
     0,
     "Combine",
     "The strip values are combined with accumulated results by appropriately using addition, "
     "multiplication, or quaternion math, based on channel type"},
    {0, "", 0, NULL, NULL},
    {NLASTRIP_MODE_ADD,
     "ADD",
     0,
     "Add",
     "Weighted result of strip is added to the accumulated results"},
    {NLASTRIP_MODE_SUBTRACT,
     "SUBTRACT",
     0,
     "Subtract",
     "Weighted result of strip is removed from the accumulated results"},
    {NLASTRIP_MODE_MULTIPLY,
     "MULTIPLY",
     0,
     "Multiply",
     "Weighted result of strip is multiplied with the accumulated results"},
    {0, NULL, 0, NULL, NULL},
};

const EnumPropertyItem rna_enum_nla_mode_extend_items[] = {
    {NLASTRIP_EXTEND_NOTHING, "NOTHING", 0, "Nothing", "Strip has no influence past its extents"},
    {NLASTRIP_EXTEND_HOLD,
     "HOLD",
     0,
     "Hold",
     "Hold the first frame if no previous strips in track, and always hold last frame"},
    {NLASTRIP_EXTEND_HOLD_FORWARD, "HOLD_FORWARD", 0, "Hold Forward", "Only hold last frame"},
    {0, NULL, 0, NULL, NULL},
};

static void rna_def_strip_fcurves(BlenderRNA *brna, PropertyRNA *cprop)
{
  StructRNA *srna;

  FunctionRNA *func;
  PropertyRNA *parm;

  RNA_def_property_srna(cprop, "NlaStripFCurves");
  srna = RNA_def_struct(brna, "NlaStripFCurves", NULL);
  RNA_def_struct_sdna(srna, "NlaStrip");
  RNA_def_struct_ui_text(srna, "NLA-Strip F-Curves", "Collection of NLA strip F-Curves");

  /* Strip.fcurves.find(...) */
  func = RNA_def_function(srna, "find", "rna_NlaStrip_fcurve_find");
  RNA_def_function_ui_description(
      func,
      "Find an F-Curve. Note that this function performs a linear scan "
      "of all F-Curves in the NLA strip.");
  RNA_def_function_flag(func, FUNC_USE_REPORTS);
  parm = RNA_def_string(func, "data_path", NULL, 0, "Data Path", "F-Curve data path");
  RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
  RNA_def_int(func, "index", 0, 0, INT_MAX, "Index", "Array index", 0, INT_MAX);

  parm = RNA_def_pointer(
      func, "fcurve", "FCurve", "", "The found F-Curve, or None if it doesn't exist");
  RNA_def_function_return(func, parm);
}

static void rna_def_nlastrip(BlenderRNA *brna)
{
  StructRNA *srna;
  PropertyRNA *prop;

  /* enum defs */
  static const EnumPropertyItem prop_type_items[] = {
      {NLASTRIP_TYPE_CLIP, "CLIP", 0, "Action Clip", "NLA Strip references some Action"},
      {NLASTRIP_TYPE_TRANSITION,
       "TRANSITION",
       0,
       "Transition",
       "NLA Strip 'transitions' between adjacent strips"},
      {NLASTRIP_TYPE_META, "META", 0, "Meta", "NLA Strip acts as a container for adjacent strips"},
      {NLASTRIP_TYPE_SOUND,
       "SOUND",
       0,
       "Sound Clip",
       "NLA Strip representing a sound event for speakers"},
      {0, NULL, 0, NULL, NULL},
  };

  /* struct definition */
  srna = RNA_def_struct(brna, "NlaStrip", NULL);
  RNA_def_struct_ui_text(srna, "NLA Strip", "A container referencing an existing Action");
  RNA_def_struct_path_func(srna, "rna_NlaStrip_path");
  RNA_def_struct_ui_icon(srna, ICON_NLA); /* XXX */

  /* name property */
  prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
  RNA_def_property_ui_text(prop, "Name", "");
  RNA_def_property_string_funcs(prop, NULL, NULL, "rna_NlaStrip_name_set");
  RNA_def_struct_name_property(srna, prop);
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */

  /* Enums */
  prop = RNA_def_property(srna, "type", PROP_ENUM, PROP_NONE);
  RNA_def_property_enum_sdna(prop, NULL, "type");
  RNA_def_property_clear_flag(
      prop, PROP_EDITABLE); /* XXX for now, not editable, since this is dangerous */
  RNA_def_property_enum_items(prop, prop_type_items);
  RNA_def_property_ui_text(prop, "Type", "Type of NLA Strip");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  prop = RNA_def_property(srna, "extrapolation", PROP_ENUM, PROP_NONE);
  RNA_def_property_enum_sdna(prop, NULL, "extendmode");
  RNA_def_property_enum_items(prop, rna_enum_nla_mode_extend_items);
  RNA_def_property_ui_text(
      prop, "Extrapolation", "Action to take for gaps past the strip extents");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  prop = RNA_def_property(srna, "blend_type", PROP_ENUM, PROP_NONE);
  RNA_def_property_enum_sdna(prop, NULL, "blendmode");
  RNA_def_property_enum_items(prop, rna_enum_nla_mode_blend_items);
  RNA_def_property_ui_text(
      prop, "Blending", "Method used for combining strip's result with accumulated result");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  /* Strip extents */
  prop = RNA_def_property(srna, "frame_start", PROP_FLOAT, PROP_TIME);
  RNA_def_property_float_sdna(prop, NULL, "start");
  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_start_frame_set", NULL);
  RNA_def_property_ui_text(prop, "Start Frame", "");
  RNA_def_property_update(
      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");

  prop = RNA_def_property(srna, "frame_end", PROP_FLOAT, PROP_TIME);
  RNA_def_property_float_sdna(prop, NULL, "end");
  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_end_frame_set", NULL);
  RNA_def_property_ui_text(prop, "End Frame", "");
  RNA_def_property_update(
      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");

  /* Blending */
  prop = RNA_def_property(srna, "blend_in", PROP_FLOAT, PROP_NONE);
  RNA_def_property_float_sdna(prop, NULL, "blendin");
  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_blend_in_set", NULL);
  RNA_def_property_ui_text(
      prop, "Blend In", "Number of frames at start of strip to fade in influence");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  prop = RNA_def_property(srna, "blend_out", PROP_FLOAT, PROP_NONE);
  RNA_def_property_float_sdna(prop, NULL, "blendout");
  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_blend_out_set", NULL);
  RNA_def_property_ui_text(prop, "Blend Out", "");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  prop = RNA_def_property(srna, "use_auto_blend", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_AUTO_BLENDS);
  RNA_def_property_boolean_funcs(prop, NULL, "rna_NlaStrip_use_auto_blend_set");
  RNA_def_property_ui_text(prop,
                           "Auto Blend In/Out",
                           "Number of frames for Blending In/Out is automatically determined from "
                           "overlapping strips");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  /* Action */
  prop = RNA_def_property(srna, "action", PROP_POINTER, PROP_NONE);
  RNA_def_property_pointer_sdna(prop, NULL, "act");
  RNA_def_property_pointer_funcs(prop, NULL, NULL, NULL, "rna_Action_id_poll");
  RNA_def_property_flag(prop, PROP_EDITABLE | PROP_ID_REFCOUNT);
  RNA_def_property_editable_func(prop, "rna_NlaStrip_action_editable");
  RNA_def_property_ui_text(prop, "Action", "Action referenced by this strip");
  RNA_def_property_update(
      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_dependency_update");

  /* Action extents */
  prop = RNA_def_property(srna, "action_frame_start", PROP_FLOAT, PROP_TIME);
  RNA_def_property_float_sdna(prop, NULL, "actstart");
  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_action_start_frame_set", NULL);
  RNA_def_property_ui_text(prop, "Action Start Frame", "First frame from action to use");
  RNA_def_property_update(
      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");

  prop = RNA_def_property(srna, "action_frame_end", PROP_FLOAT, PROP_TIME);
  RNA_def_property_float_sdna(prop, NULL, "actend");
  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_action_end_frame_set", NULL);
  RNA_def_property_ui_text(prop, "Action End Frame", "Last frame from action to use");
  RNA_def_property_update(
      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");

  /* Action Reuse */
  prop = RNA_def_property(srna, "repeat", PROP_FLOAT, PROP_NONE);
  RNA_def_property_float_sdna(prop, NULL, "repeat");
  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_repeat_set", NULL);
  /* these limits have currently be chosen arbitrarily, but could be extended
   * (minimum should still be > 0 though) if needed... */
  RNA_def_property_range(prop, 0.1f, 1000.0f);
  RNA_def_property_ui_text(prop, "Repeat", "Number of times to repeat the action range");
  RNA_def_property_update(
      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");

  prop = RNA_def_property(srna, "scale", PROP_FLOAT, PROP_NONE);
  RNA_def_property_float_sdna(prop, NULL, "scale");
  RNA_def_property_float_funcs(prop, NULL, "rna_NlaStrip_scale_set", NULL);
  /* these limits can be extended, but beyond this, we can get some crazy+annoying bugs
   * due to numeric errors */
  RNA_def_property_range(prop, 0.0001f, 1000.0f);
  RNA_def_property_ui_text(prop, "Scale", "Scaling factor for action");
  RNA_def_property_update(
      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");

  /* Strip's F-Curves */
  prop = RNA_def_property(srna, "fcurves", PROP_COLLECTION, PROP_NONE);
  RNA_def_property_collection_sdna(prop, NULL, "fcurves", NULL);
  RNA_def_property_struct_type(prop, "FCurve");
  RNA_def_property_ui_text(
      prop, "F-Curves", "F-Curves for controlling the strip's influence and timing");
  rna_def_strip_fcurves(brna, prop);

  /* Strip's F-Modifiers */
  prop = RNA_def_property(srna, "modifiers", PROP_COLLECTION, PROP_NONE);
  RNA_def_property_struct_type(prop, "FModifier");
  RNA_def_property_ui_text(
      prop, "Modifiers", "Modifiers affecting all the F-Curves in the referenced Action");

  /* Strip's Sub-Strips (for Meta-Strips) */
  prop = RNA_def_property(srna, "strips", PROP_COLLECTION, PROP_NONE);
  RNA_def_property_struct_type(prop, "NlaStrip");
  RNA_def_property_ui_text(
      prop,
      "NLA Strips",
      "NLA Strips that this strip acts as a container for (if it is of type Meta)");

  /* Settings - Values necessary for evaluation */
  prop = RNA_def_property(srna, "influence", PROP_FLOAT, PROP_FACTOR);
  RNA_def_property_range(prop, 0.0f, 1.0f);
  RNA_def_property_ui_text(
      prop, "Influence", "Amount the strip contributes to the current result");
  /* XXX: Update temporarily disabled so that the property can be edited at all!
   * Even autokey only applies after the curves have been re-evaluated,
   * causing the unkeyed values to be lost. */
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, /*"rna_NlaStrip_update"*/ NULL);

  prop = RNA_def_property(srna, "strip_time", PROP_FLOAT, PROP_TIME);
  RNA_def_property_ui_text(prop, "Strip Time", "Frame of referenced Action to evaluate");
  /* XXX: Update temporarily disabled so that the property can be edited at all!
   * Even autokey only applies after the curves have been re-evaluated,
   * causing the unkeyed values to be lost. */
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, /*"rna_NlaStrip_update"*/ NULL);

  /* TODO: should the animated_influence/time settings be animatable themselves? */
  prop = RNA_def_property(srna, "use_animated_influence", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_USR_INFLUENCE);
  RNA_def_property_boolean_funcs(prop, NULL, "rna_NlaStrip_animated_influence_set");
  RNA_def_property_ui_text(
      prop,
      "Animated Influence",
      "Influence setting is controlled by an F-Curve rather than automatically determined");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  prop = RNA_def_property(srna, "use_animated_time", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_USR_TIME);
  RNA_def_property_boolean_funcs(prop, NULL, "rna_NlaStrip_animated_time_set");
  RNA_def_property_ui_text(
      prop,
      "Animated Strip Time",
      "Strip time is controlled by an F-Curve rather than automatically determined");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  prop = RNA_def_property(srna, "use_animated_time_cyclic", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_USR_TIME_CYCLIC);
  RNA_def_property_ui_text(
      prop, "Cyclic Strip Time", "Cycle the animated time within the action start & end");
  RNA_def_property_update(
      prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_transform_update");

  /* settings */
  prop = RNA_def_property(srna, "active", PROP_BOOLEAN, PROP_NONE);
  /* can be made editable by hooking it up to the necessary NLA API methods */
  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_ACTIVE);
  RNA_def_property_ui_text(prop, "Active", "NLA Strip is active");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */

  prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_SELECT);
  RNA_def_property_ui_text(prop, "Select", "NLA Strip is selected");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */

  prop = RNA_def_property(srna, "mute", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_MUTED);
  RNA_def_property_ui_icon(prop, ICON_CHECKBOX_HLT, -1);
  RNA_def_property_ui_text(prop, "Mute", "Disable NLA Strip evaluation");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  prop = RNA_def_property(srna, "use_reverse", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_REVERSE);
  RNA_def_property_ui_text(prop,
                           "Reversed",
                           "NLA Strip is played back in reverse order (only when timing is "
                           "automatically determined)");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  prop = RNA_def_property(srna, "use_sync_length", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLASTRIP_FLAG_SYNC_LENGTH);
  RNA_def_property_ui_text(prop,
                           "Sync Action Length",
                           "Update range of frames referenced from action "
                           "after tweaking strip and its keyframes");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");
}

static void rna_api_nlatrack_strips(BlenderRNA *brna, PropertyRNA *cprop)
{
  StructRNA *srna;
  PropertyRNA *parm;
  FunctionRNA *func;

  RNA_def_property_srna(cprop, "NlaStrips");
  srna = RNA_def_struct(brna, "NlaStrips", NULL);
  RNA_def_struct_sdna(srna, "NlaTrack");
  RNA_def_struct_ui_text(srna, "Nla Strips", "Collection of Nla Strips");

  func = RNA_def_function(srna, "new", "rna_NlaStrip_new");
  RNA_def_function_flag(func,
                        FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
  RNA_def_function_ui_description(func, "Add a new Action-Clip strip to the track");
  parm = RNA_def_string(func, "name", "NlaStrip", 0, "", "Name for the NLA Strips");
  RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
  parm = RNA_def_int(func,
                     "start",
                     0,
                     INT_MIN,
                     INT_MAX,
                     "Start Frame",
                     "Start frame for this strip",
                     INT_MIN,
                     INT_MAX);
  RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
  parm = RNA_def_pointer(func, "action", "Action", "", "Action to assign to this strip");
  RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED);
  /* return type */
  parm = RNA_def_pointer(func, "strip", "NlaStrip", "", "New NLA Strip");
  RNA_def_function_return(func, parm);

  func = RNA_def_function(srna, "remove", "rna_NlaStrip_remove");
  RNA_def_function_flag(func,
                        FUNC_USE_SELF_ID | FUNC_USE_MAIN | FUNC_USE_CONTEXT | FUNC_USE_REPORTS);
  RNA_def_function_ui_description(func, "Remove a NLA Strip");
  parm = RNA_def_pointer(func, "strip", "NlaStrip", "", "NLA Strip to remove");
  RNA_def_parameter_flags(parm, PROP_NEVER_NULL, PARM_REQUIRED | PARM_RNAPTR);
  RNA_def_parameter_clear_flags(parm, PROP_THICK_WRAP, 0);
}

static void rna_def_nlatrack(BlenderRNA *brna)
{
  StructRNA *srna;
  PropertyRNA *prop;

  srna = RNA_def_struct(brna, "NlaTrack", NULL);
  RNA_def_struct_ui_text(
      srna, "NLA Track", "A animation layer containing Actions referenced as NLA strips");
  RNA_def_struct_ui_icon(srna, ICON_NLA);

  /* strips collection */
  prop = RNA_def_property(srna, "strips", PROP_COLLECTION, PROP_NONE);
  RNA_def_property_struct_type(prop, "NlaStrip");
  RNA_def_property_ui_text(prop, "NLA Strips", "NLA Strips on this NLA-track");

  rna_api_nlatrack_strips(brna, prop);

  /* name property */
  prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
  RNA_def_property_ui_text(prop, "Name", "");
  RNA_def_struct_name_property(srna, prop);
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */

  /* settings */
  prop = RNA_def_property(srna, "active", PROP_BOOLEAN, PROP_NONE);
  /* can be made editable by hooking it up to the necessary NLA API methods */
  RNA_def_property_clear_flag(prop, PROP_EDITABLE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_ACTIVE);
  RNA_def_property_ui_text(prop, "Active", "NLA Track is active");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */

  prop = RNA_def_property(srna, "is_solo", PROP_BOOLEAN, PROP_NONE);
  /* can be made editable by hooking it up to the necessary NLA API methods */
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_SOLO);
  RNA_def_property_ui_text(
      prop,
      "Solo",
      "NLA Track is evaluated itself (i.e. active Action and all other NLA Tracks in the "
      "same AnimData block are disabled)");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");
  RNA_def_property_boolean_funcs(prop, NULL, "rna_NlaTrack_solo_set");

  prop = RNA_def_property(srna, "select", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_SELECTED);
  RNA_def_property_ui_text(prop, "Select", "NLA Track is selected");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */

  prop = RNA_def_property(srna, "mute", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_MUTED);
  RNA_def_property_ui_text(prop, "Muted", "Disable NLA Track evaluation");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA | NA_EDITED, "rna_NlaStrip_update");

  prop = RNA_def_property(srna, "lock", PROP_BOOLEAN, PROP_NONE);
  RNA_def_property_boolean_sdna(prop, NULL, "flag", NLATRACK_PROTECTED);
  RNA_def_property_ui_text(prop, "Locked", "NLA Track is locked");
  RNA_def_property_update(prop, NC_ANIMATION | ND_NLA, NULL); /* this will do? */
}

/* --------- */

void RNA_def_nla(BlenderRNA *brna)
{
  rna_def_nlatrack(brna);
  rna_def_nlastrip(brna);
}

#endif
